<?php
/**
 * NetBiz Framework
 * Copyright (C) 2008 - 2009 NetBiz
 * 
 * All rights reserved.
 */

class FRM_Request
{
	/**
	 * Array holding all request parameters
	 */
	static protected $_request_data = array();
	static protected $_is_cli = FALSE;

	/**
	 * Variables for MVC mapping
	 */
	static protected $_controller = '';
	static protected $_action = '';

	// :TODO: Remove that when migration is complete
	// @see FRM_Application::run
	static public function getController()
	{
		return self::$_controller;
	}
	static public function getNameController()
	{
		$temp = explode('_', self::$_controller);
		return $temp[1];
	}
	static public function setController($value)
	{
		self::$_controller = $value;
	}
	static public function getAction()
	{
		return self::$_action;
	}

	/**
	 * Array holding original calls parameters
	 */
	static protected $_args_string = '';
	static protected $_args = array();
	static protected $_cli_args = array();
	static protected $_get = array();
	static protected $_post = array();

	/**
	 * Domain part
	 */
	static protected $_host_domain = '';
	static protected $_rewrite_base = '';


	/**
	 * Initialization function (static constructor)
	 * This function will extract parameters from the user query
	 * and build an unified parameters array.
	 *
	 * @param bool Indicate wether it's a CLI call or not
	 * @param bool If set to true, the class will throw an exception if it finds an CLI parameter
	 *   with incorrect name. If FALSE, the invalid parameter will be silently ignored. TRUE
	 *   by default.
	 * @throw FRM_Exception for invalid parameter name in CLI context
	 */
	static public function initialize($is_cli = FALSE, $exception_on_invalid_arg = TRUE)
	{
		self::$_is_cli = $is_cli;
		if ($is_cli)
		{
			$construction_order = 'C';
			self::extractCLIArgs();
		}
		else
		{
			$construction_order = FRM_Config::get('REQUEST','PARAM_ORDER') ;
			// Extract args from URI
			$uri = $_SERVER['REQUEST_URI'];
			// Escape multiple slashes as it seems to be ok for Apache
			$uri = preg_replace('#/+#','/',$uri);
			// Strip get part
			$uri = preg_replace('#(\?.*)?#','',$uri);

			// Apply rewrite rules
			self::applyRewritingRules($uri);
			self::extractArgs();

			// Save original post and get
			self::$_get = $_GET;
			self::$_post = $_POST;
		}

		if ($construction_order != 'GPA')
		{
			$construction_order = preg_replace('/[^GPAC]/','',$construction_order);
			$construction_order = preg_replace('/(.)(?=.)/','$1,',$construction_order);
			$construction_order = str_replace(array('G','P','A','C'),array('self::$_get','self::$_post','self::$_args','self::$_cli_args'),$construction_order);
			self::$_request_data = eval('return array_merge('.$construction_order.');');
		}
		else
		{
			self::$_request_data = array_merge(self::$_get,self::$_post,self::$_args);
		}
	}

	/**
	 * Apply rewriting rules to the URI. Replaces the old
	 * rewriting rules initially placed in the .htaccess file
	 * and processed by mod_rewrite.
	 *
	 * This function set the controller, the action and the args
	 * string for further processing.
	 *
	 * @param string $uri, the URI t5o rewrite, with double slashes
	 *   ( // ) already normalized and no Query String part
	 * @return none
	 */
	static protected function applyRewritingRules($uri)
	{
		// :TRICKY: better be sure what you're doing here,
		// these replace and slashes work are done for something
		
		// Extracting the rewrite base using the APPLICATION_URL
		$url_regexp = '#^(?:https?://)?'.
			'(?:(?:[0-9a-z_!~*\'().&=+$%-]+:)?[0-9a-z_!~*\'().&=+$%-]+@)?' . //user@
			'((?:[0-9]{1,3}\.){3}[0-9]{1,3}' . // IP- 199.194.52.184
			'|localhost|' . // allows either IP or domain or localhost
			'(?:[0-9a-z_!~*\'()-]+\.)*' . // tertiary domain(s)- www.
			'(?:[0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.' . // second level domain
			'[a-z]{2,6})' . // first level domain- .com or .museum
			'(?::[0-9]{1,4})?' . // port number- :80
			'((?:/?)|' . // a slash isn't required if there is no file name
			'(?:/[0-9a-z_!~*\'().;?:@&=+$,%\#-]+)+/?)$#';
		preg_match($url_regexp,FRM_Config::get('APPLICATION','URL'),$match);
		self::$_host_domain = '.'.@$match[1];
		self::$_rewrite_base = @$match[2] . '/';
		// Remove duplicate slashes
		self::$_rewrite_base = preg_replace('#/+#','/',self::$_rewrite_base);

		// Get rid of the base in the URI
		$uri = preg_replace('#^'.preg_quote(self::$_rewrite_base,'#').'#','/',$uri);

		// Get controller, action and args, and a potential FRM Call
		$c_regexp = '((?:FRM/)?[a-zA-Z_]\w*)';
		$a_regexp =	'([a-zA-Z_]\w*)';
		$args_regexp = '([^/]+(?:/[^/]+)*)/?';
		$uri_regexp = '#^/(?:'.$c_regexp.'(?:/(?:'.$a_regexp.'(?:/(?:'.$args_regexp.')?)?)?)?)?#';
		if (preg_match($uri_regexp,$uri,$matches))
		{
			// Do we have a controller?
			self::$_controller = preg_replace('#^FRM/#','FRM_',@$matches[1]); //isset($_REQUEST['m'])?$_REQUEST['m']:''; //
			// Do we have an action ?
			self::$_action = @$matches[2];
			// Do we have parameters ?
			self::$_args_string = @str_replace( array('%252F','%255C'), 
												array('%2F','%5C'), 
												$matches[3]);
		}
	}
	/**
	 * Extract args array from the args string.
	 * self::$_args_string must be set for this function
	 * to work properly.
	 *
	 * At the end of its execution, self::$_args will be
	 * available.
	 *
	 * @return none
	 */
	static protected function extractArgs()
	{
		// Extract parameters from the query
		$arr = explode("/",self::$_args_string);

		for( $i=0, $limit = count($arr); $i < $limit; $i+=2 )
		{
			self::$_args[urldecode($arr[$i])] = urldecode(@$arr[$i+1]);
		}
	}

	/**
	 * Extract CLI args from global $argv vector.
	 * This function applies specific CLI arguments rules,
	 * described in @link http://wiki.dirox.net/display/DEV/COR-FWK-APP-003
	 *
	 * @return none
	 */
	static protected function extractCLIArgs()
	{
		// Construct CLI arguments
		global $argv;
		$cliArgs = array();
		if (isset($argv))
		{
			array_shift($argv);
			$nb_arg = count($argv);
			$numeric_index = 0;
			// if $end_opts is true, that means we passed the '--' in the args sequence
			// so we need to process all following argument as unamed
			$end_opts = FALSE;

			for ($i = 0; $i < $nb_arg; ++$i)
			{
				if (!$end_opts and $argv[$i] == '--')
				{
					$end_opts = TRUE;
				}
				elseif (!$end_opts and substr($argv[$i],0,2) === '--')
				{
					$arr = explode('=',substr($argv[$i],2),2);

					// Let's validate the name
					if (self::validateParameterName($arr[0],$exception_on_invalid_arg))
					{
						// Did we use an equal sign to separate arg name and value?
						if(count($arr) > 1)
						{
							self::$_cli_args[$arr[0]] = $arr[1];
						}
						else
						{
							// The next element of argv (if existing) is the value
							if (++$i < $nb_arg)
							{
								self::$_cli_args[$arr[0]] = $argv[$i];
							}
							else
							{
								self::$_cli_args[$arr[0]] = '';
							}
						}
					}
				}
				elseif (!$end_opts and substr($argv[$i],0,1) === '-')
				{
					// Let's validate the name
					if (self::validateParameterName(substr($argv[$i],1),$exception_on_invalid_arg))
					{
						self::$_cli_args[strval(substr($argv[$i],1))] = true;
					}
				}
				else
				{
					switch($numeric_index++)
					{
						case 0 :
							self::$_controller = $argv[$i];
							break;
						case 1 :
							self::$_action = $argv[$i];
							break;
						default :
							self::$_cli_args[$numeric_index-1] = $argv[$i];
							break;
					}
				}
			}
		}
	}

	/**
	 * Validate the name of a parameter in CLI call mode
	 *
	 * @link http://extranet.dirox.net/wiki/display/DEV/COR-FWK-APP-004
	 * @param string the name to validate
	 * @param boolean if set to TRUE, an exception will be raised if the name is invalid, default to FALSE
	 * @return boolean TRUE if the name is valid, FALSE or throw an exception otherwise
	 * @throw FRM_Exception instead of FALSE when $exception_on_error is TRUE
	 */
	static protected function validateParameterName($name,$exception_on_error = FALSE)
	{
		$r = preg_match('/^[a-zA-Z_][a-zA-Z0-9_-]*$/',$name);

		if ($exception_on_error and !$r)
		{
			throw new FRM_Exception("Parameter name '$name' is invalid",FRM_EX_PARAMETER);
		}

		return $r;
	}


	/**
	 * Send a cookie to the client
	 *
	 * @param string name of the cookie to set
	 * @param mixed value of the cookie
	 * @param int time to live, in second, if 0, the cookie will
	 *   last until the end of the browser session.
	 * @return @link http://php.net/setcookie
	 */
	static public function setCookie($name,$value,$ttl = 0)
	{
		return setcookie($name, $value, ($ttl != 0 ? time() + $ttl : $ttl),
			self::$_rewrite_base, self::$_host_domain);
	}

	/**
	 * Return true if the param $varnam is set in the request data
	 * false otherwise
	 *
	 * @param string $varname, the name of the param to check the existence of
	 * @return bool
	 */
	static public function isParamSet($varname)
	{
		return array_key_exists($varname,self::$_request_data);
	}


	/**
	 * Return the value of the parameter named $varname as it is in the
	 * request.
	 * If not set in the request variables, we return the second parameter
	 * (default return value)
	 *
	 * @param string the name of the parameter to get
	 * @param mixed the value to return if the parameter is not set
	 */
	static public function get($varname,$default = FALSE)
	{
		return self::isParamSet($varname) ? self::$_request_data[$varname] : $default;

	}

	/**
	 * Set the value of a parameter.
	 * Not recommanded, but can be useful for command rewriting on live
	 *
	 * @param string the name of the parameter to set
	 * @param mixed the value to use as a value
	 * @return none
	 */
	static public function set($varname,$value)
	{
		self::$_request_data[$varname] = $value;
	}

	/**
	 * Return the value of the parameter named $varname if it's a number.
	 * If not set in the request variables, or with a wrong type,
	 * we return the second parameter
	 *
	 * @param string the name of the number parameter to get
	 * @param mixed the value to return if the parameter is not set
	 *   or is of the wrong type.
	 * @return mixed
	 */
	static public function getNumber($varname, $default = 0)
	{
		$str_value = self::get($varname,$default);

		return is_numeric($str_value) ? $str_value+0 : $default;
	}

	/**
	 * Return the value of the parameter named $varname if it's an int.
	 * If not set in the request variables, or with a wrong type,
	 * we return the second parameter
	 *
	 * @param string the name of the int parameter to get
	 * @param mixed the value to return if the parameter is not set
	 *   or is of the wrong type.
	 * @return mixed
	 */
	static public function getIntNumber($varname, $default = 0)
	{
		$val = self::getNumber($varname,$default);

		// If it's not the default value, we return it intvalued
		if ($val !== $default)
		{
			return intval($val);
		}
		else
		{
			return $val;
		}
	}

	/**
	 * Return the string value of the parameter named $varnam
	 * or $default if it's not set
	 *
	 * @param string $varname, the name of the param to return
	 * @param mixed $default the value to return if the param
	 *   is not set
	 * @return mixed
	 */
	static public function getString($varname,$default = '')
	{
		if (self::isParamSet($varname))
		{
			return strval(self::get($varname));
		}
		else
		{
			return $default;
		}
	}
    
    static public function loadSort()
	{
        $sort = array('field' => 'id', 'type'=> 'asc');
        $uri = $_SERVER['REQUEST_URI'];
        $query_string = explode('?', $uri);
        
        if (isset($query_string[1])){
            $temp = explode('&', $query_string[1]);
            foreach($temp as $s){
                $t = explode('=', $s);
                $sort[$t[0]] = $t[1];
            }
        }
        
        return $sort;	
   }

	/**
	 * Return the boolean value of the var named $varname. Accepted possible
	 * request variable values are :
	 *   'true', 'false', 'on' (gives true), 0 (gives false), integer != 0 (gives true)
	 *
	 * If not set in the request variables, we return the second parameter
	 * (default return value)
	 *
	 * @param string the name of the boolean parameter to get
	 * @param mixed the value to return if the parameter is not set or
	 *   does not evaluate as a boolean
	 * @return mixed
	 */
	static public function getBoolean($varname,$default = false)
	{
		$val = self::get($varname);

		if (strcasecmp($val,'true') == 0 or strcasecmp($val,'on') == 0)
		{
			return true;
		}
		elseif (strcasecmp($val,'false') == 0)
		{
			return false;
		}
		elseif (is_numeric($val))
		{
			return ($val+0) != 0;
		}
		else
		{
			return $default;
		}
	}

	/**
	 * Return the date value of the var named $varname.
	 * validated against the format and converted to the
	 * the given output format. If the validation failed,
	 * or if the param has not been sent,
	 * this function returns FALSE.
	 *
	 * For the definition of format strings,
	 * @link http://php.net/date
	 * Note that we're using a subset of the format modfier, containing :
	 * (d,j,m,n,y,Y,a,A,g,G,h,H,i,s) + some specials (F,S)
	 * All date characters must be prefixed by a % sign (like %a, %y)
	 */
	static public function getDateTime($varname,$in_format = NULL, $out_format = FALSE)
	{
		$val = self::getString($varname);

		// Gain some time, return false if the string is empty
		if ($val == '')
		{
			return FALSE;
		}

		// Validate the input format
		if ( ($date = FRM_DateTime::parseDate($val,$in_format)) !== FALSE )
		{
			if ($out_format === FALSE or $out_format == $in_format)
			{
				return $val;
			}
			else
			{
				return FRM_DateTime::formatDate($date,$out_format);
			}
		}

		return FALSE;
	}

	/**
	 * Returns the original args string extracted
	 * from the URI by the rewriting rules.
	 *
	 * @return the original URI's args string
	 */
	static public function getArgsString()
	{
		return self::$_args_string;
	}
}